package com.yingjia.yingjiauniversity.json.jackson;


import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import com.yingjia.yingjiauniversity.json.JsonComponent;
import com.yingjia.yingjiauniversity.json.constant.DateFormatConstant;
import com.yingjia.yingjiauniversity.json.exception.JsonDeserializerException;
import com.yingjia.yingjiauniversity.json.exception.JsonSerializerException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Map;

/**
 * <Jackson转换的Json组件类， jackson中的mapper的读写方法是线程安全的>
 *
 * @author: guoxiangxun
 * @date: Nov 26, 2015 4:09:29 PM
 * @version: v1.0
 */
public class JacksonComponentImpl implements JsonComponent {
    private Logger logger = LoggerFactory.getLogger(JacksonComponentImpl.class);

    private final String FILTER_ID = "userFilter";
    private final String[] EMPTY_FIELDS = {};    //handler fields is null, set default
    private final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private boolean isPretty;
    private ObjectMapper mapper;
    private DateFormat dateFormat;

    private static volatile JsonComponent jsonComponent;

    private JacksonComponentImpl() {
        DefaultSerializerProvider sp = new DefaultSerializerProvider.Impl();
        sp.setNullValueSerializer(NullSerializer.instance);
        BidBeanSerializerFactory bidBeanFactory = BidBeanSerializerFactory.instance;
        bidBeanFactory.setFilterId(FILTER_ID);
        mapper = new ObjectMapper(null, sp, null);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        // Uses Enum.toString() for deserialization of an Enum
        mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
        // Uses Enum.toString() for serialization of an Enum
        mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setSerializerFactory(bidBeanFactory);
        //设置格式化时间格式
        dateFormat = new SimpleDateFormat(DateFormatConstant.FULL_DATE_FORMAT);
    }

    public static JsonComponent getInstance() {
        if (jsonComponent == null) {
            synchronized (JacksonComponentImpl.class) {
                if (jsonComponent == null) {
                    jsonComponent = new JacksonComponentImpl();
                }
            }
        }

        return jsonComponent;
    }

    public boolean isPretty() {
        return isPretty;
    }

    public void setPretty(boolean isPretty) {
        this.isPretty = isPretty;
    }

    /**
     * JSON串转换为Java泛型对象，可以是各种类型，此方法最为强大。用法看测试用例。
     *
     * @param <T>
     * @param jsonString JSON字符串
     * @param tr         TypeReference,例如: new TypeReference< List<FamousUser> >(){}
     * @return List对象列表
     */
    @SuppressWarnings("unchecked")
    public <T> T toGenericObject(String jsonString, Type tr, String dateFormat) {
        return toObject(jsonString, null, null, tr, dateFormat);
    }

    /**
     * inputStream转对象
     *
     * @param is
     * @param tr
     * @param dateFormat
     * @param <T>
     * @return
     */
    public <T> T toGenericObject(InputStream is, Type tr, String dateFormat) {
        return toObject(null, is, null, tr, dateFormat);
    }

    /**
     * Json字符串转Java对象
     *
     * @param jsonString
     * @param c
     * @return
     */
    @Override
    public <T> T toObject(String jsonString, Type c, String dateFormat) {
        T rt = null;
        if (c instanceof Class) {
            rt = toObject(jsonString, null, (Class<T>) c, null, dateFormat);
        } else {
            rt = toObject(jsonString, null, null, c, dateFormat);
        }
        return rt;
    }

    /**
     * inputStream转对象
     *
     * @param <T>
     * @param is
     * @param c
     * @param dateFormat
     * @return
     */
    @Override
    public <T> T toObject(InputStream is, Type c, String dateFormat) {
        T rt = null;
        if (c instanceof Class) {
            rt = toObject(null, is, (Class<T>) c, null, dateFormat);
        } else {
            rt = toObject(null, is, null, c, dateFormat);
        }
        return rt;
    }

    @Override
    public <T> T toObject(Map map, Type c) {

        T t = null;

        try {
            if (map != null && c != null) {
                JavaType javaType = mapper.getTypeFactory().constructType(c);
                t = mapper.convertValue(map, javaType);
            }
        } catch (Exception e) {
            throw new JsonDeserializerException("map to bean deserializer exception."
                    + ", map=" + map
                    + ", class=" + c, e);
        }
        return t;
    }

    /**
     * Java对象转Json字符串
     *
     * @param object     目标对象
     * @param fields     字段
     * @param dateFormat 时间格式化
     * @return
     */
    @Override
    public String toJson(Object object, String[] fields, Boolean isExclusion, String dateFormat) {
        String jsonString = "";
        ObjectWriter writer = null;
        try {
            //设置格式化时间格式
            setMapperDateFormat(dateFormat);
            //设置过滤器,如果fields不为空，不排除任意数据
            if (fields == null) {
                fields = EMPTY_FIELDS;
            }
            SimpleBeanPropertyFilter filter = null;
            if (isExclusion != null && !isExclusion) {
                filter = SimpleBeanPropertyFilter.filterOutAllExcept(fields);
            } else {
                filter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
            }
            SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(
                    FILTER_ID, filter);
            writer = mapper.writer(filterProvider);

            if (isPretty) {
                jsonString = writer.withDefaultPrettyPrinter()
                        .writeValueAsString(object);
            } else {
                jsonString = writer.writeValueAsString(object);
            }
        } catch (IOException e) {
            throw new JsonSerializerException("bean to json serializer error., object=" + object
                    + ", fields=" + fields
                    + ", isExclusion=" + isExclusion, e);
        }
        return jsonString;
    }

    @Override
    public Map toMap(Object object, String[] fields, Boolean isExclusion) {
        Map t = null;

        ObjectWriter wr;
        try {
            if (object != null) {
                //设置过滤器,如果fields不为空，不排除任意数据
                if (fields == null) {
                    fields = EMPTY_FIELDS;
                }
                SimpleBeanPropertyFilter filter = null;
                if (isExclusion != null && !isExclusion) {
                    filter = SimpleBeanPropertyFilter.filterOutAllExcept(fields);
                } else {
                    filter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
                }
                SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(FILTER_ID, filter);
                TokenBuffer buf = new TokenBuffer(mapper.getFactory().createNonBlockingByteArrayParser());

                wr = mapper.writer().with(filterProvider);

                wr.writeValue(buf, object);
                // and provide as with a JsonParser for contents as well!
                JsonParser jp = buf.asParser();
                t = mapper.readValue(jp, Map.class);
                jp.close();
            }
        } catch (Exception e) {
            throw new JsonDeserializerException("bean to map serializer exception."
                    + ", object=" + object, e);
        }
        return t;
    }

    /**
     * Sets mapper date format.
     *
     * @param dateFormat the date format
     */
    private void setMapperDateFormat(String dateFormat) {
        if (dateFormat == null) {
            mapper.setDateFormat(this.dateFormat);
        } else {
            SimpleDateFormat sdf = (SimpleDateFormat) this.dateFormat.clone();
            sdf.applyPattern(dateFormat);
            mapper.setDateFormat(sdf);
        }
    }

    /**
     * 处理json串及inputStream转换
     *
     * @param jsonString  json string input
     * @param inputStream stream input
     * @param c           class output
     * @param type        type output
     * @param dateFormat  date format
     * @param <T>
     * @return
     */
    private <T> T toObject(String jsonString, InputStream inputStream, Class<T> c, Type type, String dateFormat) {
        T t = null;
        try {
            //设置格式化时间格式
            setMapperDateFormat(dateFormat);
            if (jsonString != null) {
                if (c != null) {
                    t = (T) mapper.readValue(jsonString, c);
                } else if (type != null) {
                    JavaType javaType = getJavaType(mapper, type);
                    t = mapper.readValue(jsonString, javaType);
                }
            } else if (inputStream != null) {
                if (c != null) {
                    t = (T) mapper.readValue(inputStream, c);
                } else if (type != null) {
                    JavaType javaType = getJavaType(mapper, type);
                    t = mapper.readValue(inputStream, javaType);
                }
            }
        } catch (Exception e) {
            throw new JsonDeserializerException("json to bean deserializer exception."
                    + ", jsonString=" + jsonString
                    + ", class=" + c, e);
        }

        return t;
    }

    /**
     * 从type获取JavaType
     *
     * @param mapper               the mapper
     * @param genericParameterType the generic parameter type
     * @return the java type
     */
    private static JavaType getJavaType(ObjectMapper mapper, Type genericParameterType) {
        return mapper.getTypeFactory().constructType(genericParameterType);

//        List<Class<?>> list = GenericReflectUtil.getGenericTypes(genericParameterType);
//        if (list == null || list.size() == 1) {
//            return mapper.getTypeFactory().constructType(genericParameterType);
//        }
//        Class<?> parametrized = list.remove(0);
//        Class<?>[] paramClasses = list.toArray(new Class[list.size()]);
//        JavaType javaType = mapper.getTypeFactory().constructParametricType(parametrized, paramClasses);
//
//        return javaType;
    }
}